React 면접 준비
작성일 : 2025.01.01
1. useState vs useReducer: 언제 무엇을 선택할까?
상태 관리는 React 애플리케이션에서 가장 중요한 부분 중 하나입니다. useState와 useReducer는 각각 다른 상황에서 유용하게 사용될 수 있습니다.
useState가 적합한 경우
- 독립적인 단순한 상태 값 관리
- 상태 업데이트 로직이 단순할 때
- 상태 변경이 2-3개의 간단한 액션으로 이뤄질 때
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(prev => prev + 1)}>
Count: {count}
</button>
);
}
useReducer가 적합한 경우
- 여러 상태가 서로 연관되어 있을 때
- 복잡한 상태 업데이트 로직이 필요할 때
- 상태 변경 패턴을 일관되게 관리해야 할 때
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { ...state, count: state.count + 1 };
case 'decrement':
return { ...state, count: state.count - 1 };
case 'reset':
return { count: 0, lastReset: Date.now() };
default:
return state;
}
}
function ComplexCounter() {
const [state, dispatch] = useReducer(reducer, { count: 0, lastReset: null });
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
<button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
</div>
);
}
2. 커스텀 훅의 강력함
커스텀 훅은 React의 코드 재사용성을 크게 향상시키는 강력한 패턴입니다.
커스텀 훅의 장점
- 로직 재사용성 향상
- 관심사 분리를 통한 코드 구조화
- 테스트 용이성
- 코드 가독성 개선
// 윈도우 크기를 추적하는 커스텀 훅
function useWindowSize() {
const [size, setSize] = useState({
width: window.innerWidth,
height: window.innerHeight
});
useEffect(() => {
const handleResize = () => {
setSize({
width: window.innerWidth,
height: window.innerHeight
});
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return size;
}
// 사용 예시
function ResponsiveComponent() {
const { width, height } = useWindowSize();
return (
<div>
Window size: {width} x {height}
</div>
);
}
3. React의 성능 최적화 트리오: memo, useMemo, useCallback
React 애플리케이션의 성능을 최적화하는 세 가지 핵심 도구에 대해 알아보겠습니다.
React.memo
컴포넌트의 props가 변경되지 않았다면 리렌더링을 방지합니다.
const ExpensiveComponent = React.memo(({ data }) => {
// 복잡한 렌더링 로직
return <div>{/* 렌더링 결과 */}</div>;
});
useMemo
계산 비용이 큰 값의 재계산을 방지합니다.
function DataProcessor({ data }) {
const processedData = useMemo(() => {
return data.map(item => expensiveCalculation(item));
}, [data]);
return <div>{processedData.map(item => <Item key={item.id} {...item} />)}</div>;
}
useCallback
함수의 불필요한 재생성을 방지합니다.
function ParentComponent({ data }) {
const handleClick = useCallback(() => {
console.log(data);
}, [data]);
return <ChildComponent onClick={handleClick} />;
}
4. useEffect: 모범 사례와 주의사항
useEffect는 React의 부수 효과를 관리하는 핵심 훅입니다.
모범 사례
- 의존성 배열 정확히 지정하기
- 클린업 함수 활용하기
- API 호출 관리하기
function DataFetcher({ url }) {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
const controller = new AbortController();
async function fetchData() {
try {
const response = await fetch(url, {
signal: controller.signal
});
const result = await response.json();
setData(result);
} catch (error) {
if (!error.name === 'AbortError') {
setError(error);
}
}
}
fetchData();
return () => {
controller.abort();
};
}, [url]);
if (error) return <div>Error: {error.message}</div>;
if (!data) return <div>Loading...</div>;
return <div>{/* 데이터 표시 */}</div>;
}
주의사항
- 무한 루프 방지하기
- 불필요한 의존성 제거하기
- 부수 효과 최소화하기
5. 코드 스플리팅과 React.lazy
대규모 애플리케이션의 성능을 향상시키기 위한 코드 스플리팅 전략을 살펴보겠습니 다.
const LazyComponent = React.lazy(() => import('./LazyComponent'));
function App() {
return (
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route
path="/dashboard"
element={<LazyComponent />}
/>
</Routes>
</Suspense>
);
}
6. useContext: 전역 상태 관리의 강력함과 주의점
Context API를 통한 상태 관리의 장단점을 알아보겠습니다.
const ThemeContext = React.createContext();
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const value = useMemo(() => ({
theme,
setTheme
}), [theme]);
return (
<ThemeContext.Provider value={value}>
{children}
</ThemeContext.Provider>
);
}
주의사항
- Context 값 변경 시 모든 소비자 컴포넌트가 리렌더링됨
- 성능 최적화를 위한 적절한 Context 분리 필요
- 상태 업데이트 로직의 중앙화 필요